package org.xlp.expression;

import org.xlp.assertion.AssertUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * Description: 实数加减乘除表达式解析器（包含括号）使用{@link BigDecimal}，当除不尽时，结果由误差
 * <br/>date: 2024/12/26 19:25
 *
 * @version 1.0
 * @author: xlp
 */
public class DecimalExpressionParser implements IExpressionParser{
    private IExpressionItemParser itemParser;

    private static final int DEFAULT_SCALE = 6;

    /**
     * 除不尽时，小数精度，默认6位
     */
    private int scale = DEFAULT_SCALE;

    /**
     * 构造函数
     * @param expression 要解析的表达式
     * @throws NullPointerException 假如第参数为null或空，则抛出该异常
     */
    public DecimalExpressionParser(String expression){
       this(expression, DEFAULT_SCALE);
    }

    /**
     * 构造函数
     * @param expression 要解析的表达式
     * @param scale
     * @throws NullPointerException 假如第参数为null或空，则抛出该异常
     */
    public DecimalExpressionParser(String expression, int scale){
        this.itemParser = new ExpressionItemParser(expression);
        setScale(scale);
    }

    /**
     * 构造函数
     * @param itemParser 把表达式解析成计算项对的解析器
     * @throws NullPointerException 假如第参数为null，则抛出该异常
     */
    public DecimalExpressionParser(IExpressionItemParser itemParser){
        this(itemParser, DEFAULT_SCALE);
    }

    /**
     * 构造函数
     * @param itemParser 把表达式解析成计算项对的解析器
     * @param scale
     * @throws NullPointerException 假如第参数为null，则抛出该异常
     */
    public DecimalExpressionParser(IExpressionItemParser itemParser, int scale){
        AssertUtils.isNotNull(itemParser, "itemParser parameter must be not null!");
        this.itemParser = itemParser;
        setScale(scale);
    }

    /**
     * 除不尽时，小数精度
     * @param scale
     */
    public void setScale(int scale) {
        this.scale = Math.max(scale, 0);
    }

    /**
     * 解析函数
     *
     * @return 返回表达式结果
     * @throws ExpressionParseException 假如表达式不合法，则抛出该异常
     */
    @Override
    public Number parse() throws ExpressionParseException {
        //数据项解析，防止重复解析
        if (itemParser.getExpressionItems() == null){
            // 还未解析表达式项，则进行解析
            itemParser.parse();
        }
        StringArrayIterator iterator = new StringArrayIterator(itemParser.getExpressionItems());
        return calculateItem(iterator);
    }

    private BigDecimal calculateItem(StringArrayIterator iterator) {
        //计算乘除法
        BigDecimal result = calculateItem2(iterator);

        while (iterator.hasNext()) {
            String next = iterator.next();
            switch (next) {
                case PLUS:
                    result = result.add(calculateItem2(iterator));
                    break;
                case MINUS:
                    result = result.subtract(calculateItem2(iterator));
                    break;
                case RIGHT_BRACKET:
                    return result;
                default:
                    result = new BigDecimal(next);
            }
        }
        return result;
    }

    private BigDecimal calculateItem2(StringArrayIterator iterator) {
        BigDecimal result =  BigDecimal.ZERO;
        while (iterator.hasNext()) {
            String next = iterator.next();
            switch (next) {
                case MULTIPLY:
                    next = iterator.next();
                    if (next.equals(LEFT_BRACKET)){
                        result = result.multiply(calculateItem(iterator));
                    } else {
                        result = result.multiply(new BigDecimal(next));
                    }
                    break;
                case DIVISION:
                    next = iterator.next();
                    if (next.equals(RIGHT_BRACKET)){
                        result = result.divide(calculateItem(iterator), scale, RoundingMode.HALF_UP);
                    } else {
                        result = result.divide(new BigDecimal(next), scale, RoundingMode.HALF_UP);
                    }
                    break;
                case LEFT_BRACKET:
                    result = calculateItem(iterator);
                    break;
                case PLUS:
                case MINUS:
                case RIGHT_BRACKET:
                    iterator.previous();
                    return result;
                default:
                    result = new BigDecimal(next);
            }
        }
        return result;
    }
}
